﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TvdP
{
    public static partial class Adapters
    {
        /// <summary>
        /// Compares two adapter components (delegates). The most specific comes first.
        /// </summary>
        internal sealed class AdapterComparer : IComparer<Delegate>
        {
            public int Compare(Delegate x, Delegate y)
            {
                int res = 0;

                if (!object.ReferenceEquals(x, y))
                {
                    res = CompareFromTypes(x, y);

                    if (res == 0)
                        //if x 'to' is assignable to y 'to'
                        //and y 'to' is not assignable to x 'to' then x is more 'complete'. x covers both the real 'To' and y
                        //(both x and y are assignable to the real 'To')
                        //In that case x comes first (-1)
                        res = CompareTypeMostAssignableFirst(x.Method.ReturnType, y.Method.ReturnType);
                }

                return res;
            }

            static int CompareFromTypes(Delegate x, Delegate y)
            {
                var xPrms = x.Method.GetParameters();
                var yPrms = y.Method.GetParameters();

                var xFromCount = xPrms.Length;
                var yFromCount = yPrms.Length;

                //delegate with most input arguments comes first
                int res =
                    xFromCount > yFromCount ? -1 :
                    yFromCount > xFromCount ? 1 :
                    0
                ;

                //only when input arguments count is 1 do we compare the input param types.
                if (res == 0 && xFromCount == 1)
                {
                    var xFrom = xPrms[0].ParameterType;
                    var yFrom = yPrms[0].ParameterType;

                    res = CompareTypeMostAssignableFirst(xFrom, yFrom);

                    if (res == 0)
                        res = CompareTypeInterfaceFirst(xFrom, yFrom);
                }

                return res;
            }

            static int CompareTypeInterfaceFirst(Type xFrom, Type yFrom)
            {

                //if x 'from' is an interface and y 'from' is not then
                //x 'from' must be specified higher up the hierarchy chain (since they are not assignable)
                //the x 'from' interface is then closer to the real 'From' so we prefer x.
                bool xFisI = xFrom.IsInterface;
                bool yFisI = yFrom.IsInterface;

                return
                    xFisI && !yFisI ? -1 :
                    yFisI && !xFisI ? 1 :
                    0
                ;
            }

            static int CompareTypeMostAssignableFirst(Type xFrom, Type yFrom)
            {
                bool xFToY = yFrom.IsAssignableFrom(xFrom);
                bool yFToX = xFrom.IsAssignableFrom(yFrom);

                //if x 'from' assignable to y 'from'
                //and y 'from' not assignable to x 'from' then x is closer to the real 'From'.
                //(The real 'From' is assignable to both x and y!)
                //In that case x comes first (-1)

                return
                    xFToY && !yFToX ? -1 :
                    yFToX && !xFToY ? 1 :
                    0
                ;
            }

            public static readonly IComparer<Delegate> _Instance = new AdapterComparer();
        }
    }
}
